package com.hmdp.utils;

import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.time.LocalDateTime;
import java.util.concurrent.TimeUnit;

/**
 * 通用Redis缓存工具类（基于StringRedisTemplate）
 * 支持TTL过期、逻辑过期、缓存穿透、缓存击穿解决方案
 */
@Component
public class RedisCacheUtil {

    @Resource
    private StringRedisTemplate stringRedisTemplate;

    // 缓存空值标识（统一用空字符串，避免JSON解析问题）
    private static final String CACHE_NULL_VALUE = "";

    /**
     * 方法1：存储缓存（TTL过期时间）
     * @param key 缓存key
     * @param value 任意Java对象（需支持JSON序列化）
     * @param ttl 过期时间
     * @param timeUnit 时间单位
     */
    public void setWithTTL(String key, Object value, long ttl, TimeUnit timeUnit) {
        String jsonStr = serialize(value);
        stringRedisTemplate.opsForValue().set(key, jsonStr, ttl, timeUnit);
    }

    /**
     * 方法2：存储缓存（逻辑过期时间，用于解决缓存击穿）
     * @param key 缓存key
     * @param value 任意Java对象（需支持JSON序列化）
     * @param expireSeconds 逻辑过期秒数
     */
    public void setWithLogicalExpire(String key, Object value, long expireSeconds) {
        // 封装逻辑过期数据
        RedisData redisData = new RedisData();
        redisData.setData(value);
        redisData.setExpireTime(LocalDateTime.now().plusSeconds(expireSeconds));
        // 序列化后存入Redis（无物理过期时间）
        String jsonStr = JSONUtil.toJsonStr(redisData);
        stringRedisTemplate.opsForValue().set(key, jsonStr);
    }

    /**
     * 方法3：查询缓存（解决缓存穿透）
     * @param key 缓存key
     * @param type 目标对象类型
     * @return 目标对象（不存在返回null）
     */
    public <T> T getWithPassThrough(String key, Class<T> type) {
        // 1. 从Redis查询缓存
        String jsonStr = stringRedisTemplate.opsForValue().get(key);
        // 2. 缓存未命中：返回null（让调用方查库）
        if (StrUtil.isBlank(jsonStr)) {
            return null;
        }
        // 3. 缓存命中空值：返回null（解决缓存穿透）
        if (CACHE_NULL_VALUE.equals(jsonStr)) {
            return null;
        }
        // 4. 缓存命中非空值：反序列化返回
        return deserialize(jsonStr, type);
    }

    /**
     * 方法4：查询缓存（逻辑过期解决缓存击穿）
     * @param key 缓存key
     * @param type 目标对象类型
     * @return 目标对象（未命中/已过期返回null，未过期返回数据）
     */
    public <T> T getWithLogicalExpire(String key, Class<T> type) {
        // 1. 从Redis查询缓存
        String jsonStr = stringRedisTemplate.opsForValue().get(key);
        // 2. 缓存未命中：返回null
        if (StrUtil.isBlank(jsonStr)) {
            return null;
        }
        // 3. 反序列化为RedisData（包含数据和逻辑过期时间）
        RedisData redisData = JSONUtil.toBean(jsonStr, RedisData.class);
        T data = deserialize(JSONUtil.toJsonStr(redisData.getData()), type);
        LocalDateTime expireTime = redisData.getExpireTime();
        // 4. 判断是否过期：未过期返回数据，已过期返回null（让调用方重建缓存）
        if (expireTime.isAfter(LocalDateTime.now())) {
            return data;
        }
        return null;
    }

    /**
     * 通用序列化：Java对象 → JSON字符串
     */
    private String serialize(Object obj) {
        if (obj == null) {
            return CACHE_NULL_VALUE;
        }
        return JSONUtil.toJsonStr(obj);
    }

    /**
     * 通用反序列化：JSON字符串 → 目标Java类型
     */
    private <T> T deserialize(String jsonStr, Class<T> type) {
        if (StrUtil.isBlank(jsonStr) || CACHE_NULL_VALUE.equals(jsonStr)) {
            return null;
        }
        return JSONUtil.toBean(jsonStr, type);
    }

    /**
     * 逻辑过期缓存数据封装类
     * 需与工具类配合使用，存储数据本身和过期时间
     */
    public static class RedisData {
        private Object data;
        private LocalDateTime expireTime;

        // getter + setter
        public Object getData() {
            return data;
        }

        public void setData(Object data) {
            this.data = data;
        }

        public LocalDateTime getExpireTime() {
            return expireTime;
        }

        public void setExpireTime(LocalDateTime expireTime) {
            this.expireTime = expireTime;
        }
    }
}